package security

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"
	"strings"
)

// ConfigManager handles secure configuration management
type ConfigManager struct {
	encryptionKey []byte
	environment   string
}

// SecureConfig represents encrypted configuration values
type SecureConfig struct {
	Database DatabaseSecureConfig `mapstructure:"database"`
	Redis    RedisSecureConfig    `mapstructure:"redis"`
	JWT      JWTSecureConfig      `mapstructure:"jwt"`
	App      AppSecureConfig      `mapstructure:"app"`
}

// DatabaseSecureConfig holds encrypted database configuration
type DatabaseSecureConfig struct {
	Host         string `mapstructure:"host"`
	Port         int    `mapstructure:"port"`
	Username     string `mapstructure:"username"`
	Password     string `mapstructure:"password"` // Will be encrypted
	Database     string `mapstructure:"database"`
	MaxOpenConns int    `mapstructure:"max_open_conns"`
	MaxIdleConns int    `mapstructure:"max_idle_conns"`
	SSLMode      string `mapstructure:"ssl_mode"`
}

// RedisSecureConfig holds encrypted Redis configuration
type RedisSecureConfig struct {
	Addr     string `mapstructure:"addr"`
	Password string `mapstructure:"password"` // Will be encrypted
	DB       int    `mapstructure:"db"`
	PoolSize int    `mapstructure:"pool_size"`
}

// JWTSecureConfig holds JWT configuration
type JWTSecureConfig struct {
	SecretKey    string `mapstructure:"secret_key"` // Will be encrypted
	ExpiryHours  int    `mapstructure:"expiry_hours"`
	RefreshHours int    `mapstructure:"refresh_hours"`
	Issuer       string `mapstructure:"issuer"`
}

// AppSecureConfig holds application configuration
type AppSecureConfig struct {
	Environment string `mapstructure:"environment"`
	Debug       bool   `mapstructure:"debug"`
	Port        int    `mapstructure:"port"`
	TrustedProxies []string `mapstructure:"trusted_proxies"`
}

// NewConfigManager creates a new secure configuration manager
func NewConfigManager() (*ConfigManager, error) {
	// Get encryption key from environment
	keyString := os.Getenv("CONFIG_ENCRYPTION_KEY")
	if keyString == "" {
		// Generate a key for development (should be set in production)
		key := make([]byte, 32)
		if _, err := rand.Read(key); err != nil {
			return nil, err
		}
		keyString = base64.StdEncoding.EncodeToString(key)
		fmt.Printf("Generated CONFIG_ENCRYPTION_KEY: %s\n", keyString)
		fmt.Println("Please set this as an environment variable in production!")
	}

	key, err := base64.StdEncoding.DecodeString(keyString)
	if err != nil {
		return nil, fmt.Errorf("invalid CONFIG_ENCRYPTION_KEY: %w", err)
	}

	if len(key) != 32 {
		return nil, errors.New("CONFIG_ENCRYPTION_KEY must be 32 bytes (base64 encoded)")
	}

	environment := os.Getenv("GIN_ENV")
	if environment == "" {
		environment = "development"
	}

	return &ConfigManager{
		encryptionKey: key,
		environment:   environment,
	}, nil
}

// LoadSecureConfig loads configuration with environment variable override support
func (cm *ConfigManager) LoadSecureConfig() (*SecureConfig, error) {
	config := &SecureConfig{}

	// Load database configuration
	config.Database = DatabaseSecureConfig{
		Host:         cm.getEnvOrDefault("DB_HOST", "127.0.0.1"),
		Port:         cm.getEnvIntOrDefault("DB_PORT", 3306),
		Username:     cm.getEnvOrDefault("DB_USERNAME", "root"),
		Password:     cm.getSecureEnv("DB_PASSWORD"), // Encrypted storage
		Database:     cm.getEnvOrDefault("DB_DATABASE", "gin_framework"),
		MaxOpenConns: cm.getEnvIntOrDefault("DB_MAX_OPEN_CONNS", 100),
		MaxIdleConns: cm.getEnvIntOrDefault("DB_MAX_IDLE_CONNS", 25),
		SSLMode:      cm.getEnvOrDefault("DB_SSL_MODE", "disable"),
	}

	// Load Redis configuration
	config.Redis = RedisSecureConfig{
		Addr:     cm.getEnvOrDefault("REDIS_ADDR", "127.0.0.1:6379"),
		Password: cm.getSecureEnv("REDIS_PASSWORD"), // Encrypted storage
		DB:       cm.getEnvIntOrDefault("REDIS_DB", 0),
		PoolSize: cm.getEnvIntOrDefault("REDIS_POOL_SIZE", 20),
	}

	// Load JWT configuration
	config.JWT = JWTSecureConfig{
		SecretKey:    cm.getSecureEnv("JWT_SECRET_KEY"), // Must be encrypted
		ExpiryHours:  cm.getEnvIntOrDefault("JWT_EXPIRY_HOURS", 1),
		RefreshHours: cm.getEnvIntOrDefault("JWT_REFRESH_HOURS", 24),
		Issuer:       cm.getEnvOrDefault("JWT_ISSUER", "gin-framework"),
	}

	// Load application configuration
	config.App = AppSecureConfig{
		Environment: cm.environment,
		Debug:       cm.getEnvBoolOrDefault("DEBUG", cm.environment == "development"),
		Port:        cm.getEnvIntOrDefault("PORT", 8080),
		TrustedProxies: cm.getEnvSliceOrDefault("TRUSTED_PROXIES", []string{}),
	}

	// Validate required encrypted fields
	if err := cm.validateSecureConfig(config); err != nil {
		return nil, err
	}

	return config, nil
}

// getSecureEnv retrieves and decrypts environment variables
func (cm *ConfigManager) getSecureEnv(key string) string {
	encryptedValue := os.Getenv(key)
	if encryptedValue == "" {
		return ""
	}

	// Try to decrypt the value
	decrypted, err := cm.decrypt(encryptedValue)
	if err != nil {
		// If decryption fails, assume it's plain text (for development)
		fmt.Printf("WARNING: Could not decrypt %s, using plain text value\n", key)
		return encryptedValue
	}

	return decrypted
}

// getEnvOrDefault gets environment variable or default value
func (cm *ConfigManager) getEnvOrDefault(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

// getEnvIntOrDefault gets integer environment variable or default value
func (cm *ConfigManager) getEnvIntOrDefault(key string, defaultValue int) int {
	if value := os.Getenv(key); value != "" {
		if intValue, err := strconv.Atoi(value); err == nil {
			return intValue
		}
	}
	return defaultValue
}

// getEnvBoolOrDefault gets boolean environment variable or default value
func (cm *ConfigManager) getEnvBoolOrDefault(key string, defaultValue bool) bool {
	if value := os.Getenv(key); value != "" {
		if boolValue, err := strconv.ParseBool(value); err == nil {
			return boolValue
		}
	}
	return defaultValue
}

// getEnvSliceOrDefault gets slice environment variable or default value
func (cm *ConfigManager) getEnvSliceOrDefault(key string, defaultValue []string) []string {
	if value := os.Getenv(key); value != "" {
		return strings.Split(value, ",")
	}
	return defaultValue
}

// encrypt encrypts a plain text string
func (cm *ConfigManager) encrypt(plaintext string) (string, error) {
	block, err := aes.NewCipher(cm.encryptionKey)
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// decrypt decrypts an encrypted string
func (cm *ConfigManager) decrypt(ciphertext string) (string, error) {
	data, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}

	block, err := aes.NewCipher(cm.encryptionKey)
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonceSize := gcm.NonceSize()
	if len(data) < nonceSize {
		return "", errors.New("ciphertext too short")
	}

	nonce, ciphertext_bytes := data[:nonceSize], data[nonceSize:]
	plaintext, err := gcm.Open(nil, nonce, ciphertext_bytes, nil)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}

// validateSecureConfig validates that required secure configuration is present
func (cm *ConfigManager) validateSecureConfig(config *SecureConfig) error {
	if cm.environment == "production" {
		if config.JWT.SecretKey == "" {
			return errors.New("JWT_SECRET_KEY is required in production")
		}
		if config.Database.Password == "" {
			return errors.New("DB_PASSWORD is required in production")
		}
	}
	return nil
}

// EncryptAndPrintConfig helper for generating encrypted configuration values
func (cm *ConfigManager) EncryptAndPrintConfig(key, value string) error {
	encrypted, err := cm.encrypt(value)
	if err != nil {
		return err
	}
	fmt.Printf("Encrypted %s: %s\n", key, encrypted)
	return nil
}